"use strict";

//import {loadTexture} from '../utils/glutils.js';
import Main from './main.js';
import Mirror from '../objs/mirror.js';
import {tempbuffer} from '../utils/glutils.js';
import {reflect} from '../utils/calc.js';
import {randMirrors, randSource, randPoint, randWall}  from '../utils/generator.js';

const cc = Math.sqrt(window.screen.height*window.screen.height+window.screen.width*window.screen.width);
const _first = 8;
const _off = 16*Float32Array.BYTES_PER_ELEMENT;

function drawLine(target, len, src, step) {
  for (let i=0; i<len; i+=step) {
    let j = src.slice(i, Math.min(len, i+16));
    tempbuffer.set(j);
    gl.bufferSubData(gl.ARRAY_BUFFER, _off, tempbuffer);
    gl.drawArrays(target, _first, j.length/2);
  }
}

export default class Game {
  #rectx;
  #recty;
  #mirrors;
  #curr = -1;
  #selected = 0;
  #st_src = [0, 0]; // last light source
  #dr = [0, 0];
  #receiver = [0, 0];
  #lits = [0, 0, 0, 0];  // lights direction array
  #walls = [];
  #valid_n = 2;
  #score = 0;
  static #instance;

  onclick = (e) => {
    let x = e.clientX;
    let y = e.clientY;
    if (x>=this.#rectx[0] && x<=this.#rectx[1]
      && y>=this.#recty[0] && y<=this.#recty[1]) {
      Main.getInstance().display();
    } else {
      for (let i=0; i<this.#mirrors.length; i++) {
		let xx = x-this.#mirrors[i].x;
		let yy = y-this.#mirrors[i].y; 
        if (xx*xx+yy*yy<=Mirror.r*Mirror.r) {
			if (this.#curr != i) {
				this.#curr = i;
				this.invalidate();
			}
			return;
		}
      }
	  if (this.#curr !== -1) {
		  this.#curr = -1;
		  this.invalidate();
	  }
    }
  };

  ontouchstart = (e) => {
	if (this.#curr === -1)
	  this.#selected = 0;
	else {
      let xx = e.touches[0].clientX - this.#mirrors[this.#curr].x;
      let yy = e.touches[0].clientY - this.#mirrors[this.#curr].y;
	  xx = xx*xx+yy*yy;
	  if (xx<=Mirror.r*Mirror.r)
	    this.#selected = 1;
	  else
	    this.#selected = 2;
	}
  };

  ontouchmove = (e) => {
	if (this.#selected !== 0) {
	  let x = e.touches[0].clientX;
	  let y = e.touches[0].clientY;
	  if (this.#selected === 1) {
	    this.#mirrors[this.#curr].x = x;
	    this.#mirrors[this.#curr].y = y;
	  } else {
		y -= this.#mirrors[this.#curr].y;
		x -= this.#mirrors[this.#curr].x;
		vec2.normalize(this.#mirrors[this.#curr].nor, [-y, x]);
	  }
	  // calc crosses
	  let i, j, idx, tx, ty, l1, l2;
	  this.#valid_n = 1;
	  let lst_src = [this.#st_src[0], this.#st_src[1]];
	  let dr = [this.#dr[0], this.#dr[1]];
	  while (true) {
		let lsx = cc, lsy = cc, wx, wy;
		idx = -1;
		i = false;
        for (j=this.#mirrors.length-1; j>=0; j--) {
		  //let nor = [Math.sin(k.rad), -Math.cos(k.rad)]
		  let k = this.#mirrors[j];
		  wx = -Mirror.r*k.nor[1];
		  wy = Mirror.r*k.nor[0];
		  x = k.x - lst_src[0];
		  y = k.y - lst_src[1];
		  l1 = dr[1]*(x+wx) - dr[0]*(y+wy);
		  l2 = dr[1]*(x-wx) - dr[0]*(y-wy);
		  if (l1*l2<0) {
			l1 = 2*l1/(l1-l2) - 1;
			// then calc the end pos and put
			x = k.x - wx*l1;
			wx = (x-lst_src[0])*dr[0];
			y = k.y - wy*l1;
			wy = (y-lst_src[1])*dr[1];
			if (wx+wy < 1e-6)
			  continue; // not the positive direction
			// update the nearest point
			if (wx<lsx || wy<lsy) {
			  lsx = wx;
			  lsy = wy;
			  idx = j;
			  tx = x;
			  ty = y;
			}
		  }
		}
		for (j=this.#walls.length-4; j>=0; j-=4) {
		  x = dr[1]*lst_src[0]-dr[0]*lst_src[1];
		  //y = this.#walls
		  l1 = dr[1]*this.#walls[j] - dr[0]*this.#walls[j+1]-x;
		  l2 = dr[1]*this.#walls[j+2] - dr[0]*this.#walls[j+3]-x;
		  if (l1*l2<0) {
			l1 = l1/(l1-l2);
			l2 = 1 - l1;
			// then calc the end pos and put
			x = this.#walls[j]*l2+this.#walls[j+2]*l1;
			wx = (x-lst_src[0])*dr[0];
			y = this.#walls[j+1]*l2+this.#walls[j+3]*l1;
			wy = (y-lst_src[1])*dr[1];
			//console.log(l1, l2, x, y);
			if (wx+wy < 1e-6)
			  continue; // not the positive direction
			// update the nearest point
			if (wx<lsx || wy<lsy) {
			  i = true;
			  lsx = wx;
			  lsy = wy;
			  idx = j;
		      tx = x;
			  ty = y;
			}
		  }
		}
		j = lsx+lsy;
	    l1 = this.#receiver[0]-lst_src[0];
		l2 = this.#receiver[1]-lst_src[1];
		if (Math.abs(dr[1]*l1-dr[0]*l2) <= 6) {
		  lsx = dr[0]*l1 + dr[1]*l2;
		  if (-6 <= lsx && lsx <= j+6)
			this.addScore();
		}
		if (idx===-1)
		  break;
		lst_src[0] = tx;
		lst_src[1] = ty;
		this.#lits[this.#valid_n*2] = tx;
		this.#lits[this.#valid_n*2+1] = ty;
        this.#valid_n++;
		if (i) { // blocked by wall
		  this.invalidate();
		  return;
		}
		reflect(dr, dr, this.#mirrors[idx].nor);
	  }
	  this.#lits[this.#valid_n*2] = lst_src[0]+dr[0]*cc;
	  this.#lits[this.#valid_n*2+1] = lst_src[1]+dr[1]*cc;
	  this.#valid_n++;
	  this.invalidate();
	}
  };

  static getInstance() {
    if (!Game.#instance)
      Game.#instance = new Game();
    return Game.#instance;
  }

  constructor() {
    this.#rectx = [32, 56];
    this.#recty = [54, 78];

    this.array = new Float32Array([
      32, 54, 0.375, 0,
      56, 54, 0.46875, 0,
      56, 78, 0.46875, 0.09375,
      32, 78, 0.375, 0.09375
    ]);
    gl.bufferData(gl.ARRAY_BUFFER, 32*Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW);
    this.#mirrors = randMirrors(5);
	randSource(this.#st_src, this.#dr);
	randPoint(this.#receiver);
	this.#lits[0] = this.#st_src[0];
	this.#lits[1] = this.#st_src[1];
	this.#lits[2] = this.#st_src[0] + cc*this.#dr[0];
	this.#lits[3] = this.#st_src[1] + cc*this.#dr[1];
	this.init();
  }

  init() {
    //gl.lineWidth()
    /*const tex = gl.createTexture();
    const usamp = gl.getUniformLocation(gl.program, 'u_samp');
    let logo = new Image();
    logo.onload = ()=>{
      loadTexture(gl, tex, logo, usamp);
      this.display();
    };
    logo.src = './src/handle.webp';*/
  }

  invalidate() {
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.array);
	gl.vertexAttribPointer(gl.a_Position, 2, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT*2, 0);
	gl.disableVertexAttribArray(gl.texloc);
	let i, j, l;
	// draw light
	gl.uniform1i(gl.colorloc, 2);
	drawLine(gl.LINE_STRIP, this.#valid_n*2, this.#lits, 14);
	/*for (i=0; i<l; i+=14) {
	  j = this.#lits.slice(i, Math.min(l, i+16));
	  tempbuffer.set(j);
	  gl.bufferSubData(gl.ARRAY_BUFFER, _off, tempbuffer);
	  gl.drawArrays(gl.LINE_STRIP, _first, j.length/2);
	}*/
	// draw walls
	gl.uniform1i(gl.colorloc, 5);
	drawLine(gl.LINES, this.#walls.length, this.#walls, 16);
	/*l = this.#walls.length;
	for (i=0; i<l; i+=16) {
	  j = this.#walls.slice(i, Math.min(l, i+16));
	  tempbuffer.set(j);
	  gl.bufferSubData(gl.ARRAY_BUFFER, _off, tempbuffer);
	  gl.drawArrays(gl.LINES, _first, j.length/2);
	}*/
	// draw mirrors
	gl.uniform1i(gl.colorloc, 1);
    j = 0;
	l = this.#mirrors.length;
    while (j<l) {
	  i = 0;
	  while (j<l && i<4) {
        this.#mirrors[j].putBuffer(i);
		i++;
		j++;
	  }
	  gl.bufferSubData(gl.ARRAY_BUFFER, _off, tempbuffer);
      gl.drawArrays(gl.LINES, _first, i*2);
    }
	gl.uniform1i(gl.colorloc, 4);
	tempbuffer.set(this.#receiver);
	if (this.#curr !== -1)
	  this.#mirrors[this.#curr].setHandle();
	gl.bufferSubData(gl.ARRAY_BUFFER, _off, tempbuffer);
	gl.drawArrays(gl.POINTS, _first, 1);
	if (this.#curr != -1) {
		gl.uniform1i(gl.colorloc, 3);
		gl.drawArrays(gl.POINTS, _first+1, 2);
	}
	gl.uniform1i(gl.colorloc, 0);
	gl.vertexAttribPointer(gl.a_Position, 2, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT*4, 0);
	gl.enableVertexAttribArray(gl.texloc);
	gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
  }

  display() {
	this.invalidate();
    gl.canvas.onmyclick = this.onclick;
    gl.canvas.ontouchstart = this.ontouchstart;
	gl.canvas.ontouchmove = this.ontouchmove;
  }

  addScore() {
    randPoint(this.#receiver);
    randWall(this.#walls);
	console.log(++this.#score);
  }
}